package dal

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"time"

	"github.com/spf13/cast"

	"gorm.io/plugin/dbresolver"

	"code.byted.org/bytertc/whiteboard_server/biz/dal/ddl"
	"code.byted.org/gopkg/logs/v2/log"

	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"
	"code.byted.org/bytertc/whiteboard_server/biz/dal/spec"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/kv"
	"code.byted.org/bytertc/whiteboard_server/biz/service/object"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/proto"
	"gorm.io/gorm"
)

const EmptyPrePageID = ""

func genZoomBoardKey(unique spec.BoardUnique) string {
	return fmt.Sprintf("zoomBoardInfo:%s:%s:%d", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId())
}

func genPptMediaBoardKey(unique spec.BoardUnique, pageID string) string {
	return fmt.Sprintf("pptMediaBoardInfo:%s:%s:%d:%s", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), pageID)
}

func genPptMediaPageField(mediaID string) string {
	return mediaID
}

func genPptTriggerBoardKey(unique spec.BoardUnique, pageID string) string {
	return fmt.Sprintf("pptTriggerBoardInfo:%s:%s:%d:%s", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), pageID)
}

func genPptTriggerPageField(shapeID string) string {
	return shapeID
}

func NewRepo(cache kv.Storage, db *gorm.DB) Repo {
	return &repo{cache: cache, db: db}
}

type repo struct {
	cache kv.Storage
	db    *gorm.DB
}

func (r *repo) GetPages(ctx context.Context, unique spec.BoardUnique, pageIds []string) ([]*object.Page, error) {
	var ps []ddl.Pages
	query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TablePages).
		Where("app_id = ? and room_id = ? and board_id = ?",
			unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId())
	if len(pageIds) != 0 {
		query = query.Where("page_id in ?", pageIds)
	}
	err := query.Find(&ps).Error
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		return nil, errors.WithStack(err)
	}
	//排序
	ps = r.pageSort(ps)
	pageInfos := make([]*object.Page, len(ps))
	for index := range ps {
		pageInfos[index], err = object.NewPage(ps[index].ControlCount, ps[index].Data)
		if err != nil {
			return nil, err
		}
	}
	return pageInfos, nil
}
func (r *repo) GetPageCount(ctx context.Context, unique spec.BoardUnique) (int32, error) {
	Count := sql.NullInt64{}
	err := r.db.WithContext(ctx).Table(ddl.TablePages).Where(
		"app_id = ? and room_id = ? and board_id = ? and deleted_at is null",
		unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId()).
		Select("count(*)").Row().Scan(&Count)
	if errors.Is(err, sql.ErrNoRows) {
		return 0, nil
	}
	return int32(Count.Int64), biz_error.DatabaseError.Wrap(errors.WithStack(err))
}

// 回放用读从库
func (r *repo) GetFirstBoardId(ctx context.Context, unique spec.RoomUnique) (int32, error) {
	var bs []ddl.Boards
	query := r.db.WithContext(ctx).Table(ddl.TableBoards).
		Where("app_id = ? and room_id = ?", unique.GetAppId(), unique.GetRoomId()).Order("created_at").Limit(1)
	err := query.Find(&bs).Error
	if err != nil {
		return 0, errors.WithStack(err)
	}
	if len(bs) == 0 {
		return 0, biz_error.NotFound.Wrap(errors.New("not exist"))
	}
	return bs[0].BoardId, nil
}

func (r *repo) GetBoards(ctx context.Context, unique spec.RoomUnique, boardIds []int32) ([]*v1.BoardInfo, error) {
	var bs []ddl.Boards
	query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TableBoards).
		Where("app_id = ? and room_id = ?", unique.GetAppId(), unique.GetRoomId())
	if len(boardIds) != 0 {
		query = query.Where("board_id in ?", boardIds)
	}
	err := query.Find(&bs).Error
	if err != nil {
		return nil, errors.WithStack(err)
	}
	boardInfos := make([]*v1.BoardInfo, len(bs))
	for index := range bs {
		stamp := cast.ToUint64(bs[index].CreatedAt.Unix())
		boardInfos[index] = &v1.BoardInfo{
			BoardId:           bs[index].BoardId,
			CurrentPageId:     bs[index].CurrentPage,
			CurrentStep:       &bs[index].CurrentStep,
			ControlCount:      map[string]int32{},
			Status:            v1.BoardInfo_StatusType(int32(bs[index].Status)),
			BoardControlCount: &(bs[index].ControlCount),
			CreateTimestamp:   &stamp,
		}
		info := &v1.BoardInfo{}
		err := proto.Unmarshal(bs[index].Data, info)
		if err != nil {
			log.V1.CtxError(ctx, "boards table data Unmarshal err:%+v,board:%d", err, bs[index].BoardId)
			continue
		}
		boardInfos[index].BkInfo = info.BkInfo
		boardInfos[index].BoardName = info.BoardName
		boardInfos[index].InitialBoardInfo = info.InitialBoardInfo
		boardInfos[index].PptInfo = info.PptInfo

	}
	return boardInfos, nil
}

func (r *repo) CreateBoard(ctx context.Context, unique spec.BoardUnique, initBoard *v1.BoardInfo, initialPages []*v1.PageInfo) error {
	bs, err := proto.Marshal(initBoard)
	if err != nil {
		return biz_error.EncodeError.Wrap(errors.WithStack(err))
	}
	boardInfo := &v1.BoardInfo{
		InitialBoardInfo: bs,
		BoardName:        initBoard.BoardName,
		PptInfo:          initBoard.PptInfo,
	}
	if initBoard.BoardName != nil && *initBoard.BoardName != "" {
		if len([]rune(*initBoard.BoardName)) > 100 {
			return biz_error.InvalidParam.Wrap(errors.Errorf("invalid boardName length"))
		}
		/*
			matched, _ := regexp.MatchString("^[\u4e00-\u9fa5_a-zA-Z0-9-]+$", *initBoard.BoardName)
			if !matched {
				return biz_error.InvalidParam.Wrap(errors.Errorf("invalid boardName"))
			}
		*/
	}

	data, err := proto.Marshal(boardInfo)
	if err != nil {
		return biz_error.EncodeError.Wrap(errors.WithStack(err))
	}
	board := &ddl.Boards{
		AppId:       unique.GetAppId(),
		RoomId:      unique.GetRoomId(),
		BoardId:     unique.GetBoardId(),
		Status:      int(ddl.Active),
		CurrentPage: initialPages[0].PageId,
		CurrentStep: *initBoard.CurrentStep,
		Data:        data,
	}

	db := r.db.Begin()
	defer func() {
		if err != nil {
			db.Rollback()
		} else {
			db.Commit()
		}
	}()
	err = db.WithContext(ctx).Table(ddl.TableBoards).Create(board).Error
	if IsDuplicateError(err) {
		return biz_error.Conflict.Wrap(errors.WithStack(err))
	}
	_, err = r.createPages(ctx, unique, initialPages, EmptyPrePageID)
	if err != nil {
		return err
	}
	exist, err := r.RoomExist(ctx, unique)
	if err != nil {
		return err
	}
	if !exist {
		err = r.CreateRoom(ctx, unique)
		if IsDuplicateError(err) {
			return nil
		}
	}
	return err
}
func (r *repo) CloseBoard(ctx context.Context, unique spec.RoomUnique, boardId int32) (ok bool, err error) {
	result := r.db.WithContext(ctx).Table(ddl.TableBoards).Where("app_id = ? and room_id = ? and board_id = ? and status = 0",
		unique.GetAppId(), unique.GetRoomId(), boardId).
		Update("status", int(ddl.Inactive))
	return result.RowsAffected > 0, biz_error.DatabaseError.Wrap(errors.WithStack(result.Error))
}
func (r *repo) CloseBoards(ctx context.Context, unique spec.RoomUnique) (err error) {
	result := r.db.WithContext(ctx).Table(ddl.TableBoards).Where("app_id = ? and room_id = ?",
		unique.GetAppId(), unique.GetRoomId()).
		Update("status", int(ddl.Inactive))
	return biz_error.DatabaseError.Wrap(errors.WithStack(result.Error))
}

func (r *repo) UpdateBoard(ctx context.Context, unique spec.BoardUnique, oldCount, addition int32, info *v1.BoardInfo) error {
	data, err := proto.Marshal(info)
	if err != nil {
		return biz_error.EncodeError.Wrap(errors.WithStack(err))
	}
	result := r.db.WithContext(ctx).Table(ddl.TableBoards).
		Where("app_id = ? and room_id = ? and board_id = ? and control_count = ?",
			unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), oldCount).
		Updates(&ddl.Boards{
			ControlCount: oldCount + addition,
			Data:         data,
		})
	if result.Error != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(result.Error))
	}
	if result.RowsAffected == 0 {
		return biz_error.Conflict.Wrap(
			errors.Errorf("fail to update board %d to ctlCnt %d", unique.GetBoardId(), oldCount+addition))
	}
	return nil
}

func (r *repo) FlipPage(ctx context.Context, unique spec.BoardUnique, currentPage string) error {
	err := r.db.WithContext(ctx).Table(ddl.TableBoards).
		Where("app_id = ? and room_id = ? and board_id = ?",
			unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId()).
		Updates(map[string]interface{}{
			"current_page": currentPage,
			"current_step": 0,
		}).Error
	return biz_error.DatabaseError.Wrap(errors.WithStack(err))
}

func (r *repo) ZoomBoard(ctx context.Context, unique spec.BoardUnique, info *v1.ZoomInfo) error {
	cacheKey := genZoomBoardKey(unique)
	if info == nil {
		log.V1.CtxWarn(ctx, "ZoomBoard info is nil cacheKey:%s", cacheKey)
		return nil
	}
	val, err := json.Marshal(info)
	if err != nil {
		log.V1.CtxError(ctx, "ZoomBoard Marshal info err:%+v cacheKey:%s", err, cacheKey)
		return err
	}
	_, err = r.cache.Set(ctx, cacheKey, string(val), time.Hour*12)
	if err != nil {
		log.V1.CtxError(ctx, "ZoomBoard set info err:%+v cacheKey:%s", err, cacheKey)
		return err
	}
	return nil
}

func (r *repo) GetZoomInfo(ctx context.Context, unique spec.BoardUnique) (*v1.ZoomInfo, error) {
	cacheKey := genZoomBoardKey(unique)
	val, err := r.cache.Get(ctx, cacheKey)
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		log.V1.CtxError(ctx, "ZoomBoard get info err:%+v cacheKey:%s", err, cacheKey)
		return nil, err
	}
	var info v1.ZoomInfo
	err = json.Unmarshal([]byte(val), &info)
	return &info, err
}

func (r *repo) PptMediaChange(ctx context.Context, unique spec.BoardUnique, change *v1.PptMediaChange) error {
	if change == nil {
		log.V1.CtxWarn(ctx, "pptMediaBoard info is nil")
		return nil
	}
	cacheKey := genPptMediaBoardKey(unique, change.GetCurrentPageId())
	info := &v1.PptMediaInfo{
		MediaId:       change.MediaId,
		IsFullScreen:  change.IsFullScreen,
		CurrentPageId: change.CurrentPageId,
		MediaStatus:   (*v1.PptMediaInfo_MediaPlayStatus)(change.MediaStatus),
		Timestamp:     change.Timestamp,
		Source:        change.Source,
	}
	if change.CurrentTime == nil {
		info.CurrentTime = nil
	} else {
		curTime := int32(*change.CurrentTime)
		info.CurrentTime = &curTime
	}
	val, err := json.Marshal(info)
	if err != nil {
		log.V1.CtxError(ctx, "PptMediaChange Marshal info err:%+v cacheKey:%s", err, cacheKey)
		return err
	}
	_, err = r.cache.HSet(ctx, cacheKey, genPptMediaPageField(change.GetMediaId()), string(val))
	if err != nil {
		log.V1.CtxError(ctx, "PptMediaChange set info err:%+v cacheKey:%s", err, cacheKey)
		return err
	}
	//刷新过期时间
	r.cache.Expire(ctx, cacheKey, time.Hour*12)
	return nil
}

func (r *repo) GetPptMediaInfo(ctx context.Context, unique spec.BoardUnique, pageID string) ([]*v1.PptMediaInfo, error) {
	cacheKey := genPptMediaBoardKey(unique, pageID)
	val, err := r.cache.HGetAll(ctx, cacheKey)
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		log.V1.CtxError(ctx, "GetPptMediaInfo get info err:%+v cacheKey:%s", err, cacheKey)
		return nil, err
	}
	infos := make([]*v1.PptMediaInfo, 0, len(val))
	for _, data := range val {
		info := &v1.PptMediaInfo{}
		err = json.Unmarshal([]byte(data), info)
		if err != nil {
			log.V1.CtxError(ctx, "GetPptMediaInfo unmarshal info err:%+v cacheKey:%s", err, cacheKey)
			return nil, err
		}
		infos = append(infos, info)
	}
	return infos, nil
}

func (r *repo) PptTriggerChange(ctx context.Context, unique spec.BoardUnique, change *v1.PptTriggerChange) error {
	if change == nil {
		log.V1.CtxWarn(ctx, "pptMediaBoard info is nil")
		return nil
	}
	cacheKey := genPptTriggerBoardKey(unique, change.GetCurrentPageId())
	info := &v1.PptTriggerInfo{
		ShapeId:       change.ShapeId,
		SlideCategory: change.SlideCategory,
		CurrentStep:   change.CurrentStep,
	}
	val, err := json.Marshal(info)
	if err != nil {
		log.V1.CtxError(ctx, "PptTriggerChange Marshal info err:%+v cacheKey:%s", err, cacheKey)
		return err
	}
	_, err = r.cache.HSet(ctx, cacheKey, genPptTriggerPageField(change.GetShapeId()), string(val))
	if err != nil {
		log.V1.CtxError(ctx, "PptTriggerChange set info err:%+v cacheKey:%s", err, cacheKey)
		return err
	}
	//刷新过期时间
	r.cache.Expire(ctx, cacheKey, time.Hour*12)
	return nil
}

func (r *repo) GetPptTriggerInfo(ctx context.Context, unique spec.BoardUnique, pageID string) ([]*v1.PptTriggerInfo, error) {
	cacheKey := genPptTriggerBoardKey(unique, pageID)
	val, err := r.cache.HGetAll(ctx, cacheKey)
	if err != nil {
		if IsNotFoundError(err) {
			return nil, nil
		}
		log.V1.CtxError(ctx, "GetPptTriggerInfo get info err:%+v cacheKey:%s", err, cacheKey)
		return nil, err
	}
	infos := make([]*v1.PptTriggerInfo, 0, len(val))
	for _, data := range val {
		info := &v1.PptTriggerInfo{}
		err = json.Unmarshal([]byte(data), info)
		if err != nil {
			log.V1.CtxError(ctx, "GetPptTriggerInfo unmarshal info err:%+v cacheKey:%s", err, cacheKey)
			return nil, err
		}
		infos = append(infos, info)
	}
	return infos, nil
}

func (r *repo) StepChange(ctx context.Context, unique spec.BoardUnique, info *v1.PptStepChange) error {
	err := r.db.WithContext(ctx).Table(ddl.TableBoards).
		Where("app_id = ? and room_id = ? and board_id = ?",
			unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId()).
		Update("current_step", info.CurrentStep).Error
	return biz_error.DatabaseError.Wrap(errors.WithStack(err))
}

func (r *repo) CreatePages(ctx context.Context, unique spec.BoardUnique, infos []*v1.PageInfo, prePageId string) (int64, error) {
	return r.createPages(ctx, unique, infos, prePageId)
}

func (r *repo) createPages(ctx context.Context, unique spec.BoardUnique, infos []*v1.PageInfo, prePageId string) (int64, error) {
	var err error
	if prePageId != EmptyPrePageID {
		var fps []ddl.Pages
		query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TablePages).
			Where("app_id = ? and room_id = ? and board_id = ? and page_id=?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), prePageId)
		err = query.Find(&fps).Error
		if err != nil {
			if IsNotFoundError(err) {
				return 0, biz_error.NotFound.Wrap(errors.Errorf("invalid prePageId:%+v", prePageId))
			}
			return 0, biz_error.DatabaseError.Wrap(errors.WithStack(err))
		}
		if len(fps) == 0 {
			return 0, biz_error.NotFound.Wrap(errors.Errorf("invalid prePageId:%+v", prePageId))
		}
	}
	currentPrePageId := prePageId
	ps := make([]*ddl.Pages, 0, len(infos))
	for _, info := range infos {
		if info.PageId == EmptyPrePageID {
			log.V1.CtxWarn(ctx, "empty page id,app_id:%s,room_id:%s,board_id:%s,page_id:%s,page info:%+v", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), info.PageId, info)
		}
		data, err := proto.Marshal(info)
		if err != nil {
			return 0, biz_error.EncodeError.Wrap(errors.WithStack(err))
		}
		log.V1.CtxInfo(ctx, "room_id:%s,board_id:%s,page_id:%s,data:%+v,ppt_info:%#v", unique.GetRoomId(), unique.GetBoardId(), info.PageId, data, info.PptInfo)
		page := &ddl.Pages{
			AppId:        unique.GetAppId(),
			RoomId:       unique.GetRoomId(),
			BoardId:      unique.GetBoardId(),
			PageId:       info.PageId,
			PrePageId:    currentPrePageId,
			ControlCount: object.InitialActionCount,
			Data:         data,
		}
		if info.Extra != nil {
			page.Extra = *info.Extra
		}
		ps = append(ps, page)
		currentPrePageId = info.PageId
	}
	db := r.db.Begin()
	defer func() {
		if err != nil {
			db.Rollback()
		} else {
			db.Commit()
		}
	}()
	err = db.WithContext(ctx).Table(ddl.TablePages).Where("app_id = ? and room_id = ? and board_id = ? and pre_page_id=?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), prePageId).Update(
		"pre_page_id", currentPrePageId).Error
	if err != nil {
		return 0, biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	res := db.WithContext(ctx).Table(ddl.TablePages).Create(ps)
	rows, err := res.RowsAffected, res.Error
	if IsDuplicateError(err) {
		return 0, biz_error.Conflict.Wrap(errors.WithStack(err))
	}

	return rows, biz_error.DatabaseError.Wrap(errors.WithStack(err))
}

func (r *repo) UpdatePage(ctx context.Context, unique spec.PageUnique, oldCount, addition int32, info *v1.PageInfo) error {
	data, err := proto.Marshal(info)
	if err != nil {
		return biz_error.EncodeError.Wrap(errors.WithStack(err))
	}
	result := r.db.WithContext(ctx).Table(ddl.TablePages).
		Where("app_id = ? and room_id = ? and board_id = ? and page_id = ? and control_count = ? and deleted_at is null",
			unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), unique.GetPageId(), oldCount).
		Updates(&ddl.Pages{
			ControlCount: oldCount + addition,
			Data:         data,
		})
	if result.Error != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(result.Error))
	}
	if result.RowsAffected == 0 {
		return biz_error.Conflict.Wrap(
			errors.Errorf("fail to update page %s to ctlCnt %d", unique.GetPageId(), oldCount+addition))
	}
	return nil
}

func (r *repo) GetExtraCategory(ctx context.Context, unique spec.BoardUnique) ([]string, error) {
	var category []string
	result := r.db.WithContext(ctx).Table(ddl.TablePages).
		Where("app_id = ? and room_id = ? and board_id = ? and deleted_at is null",
			unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId()).Distinct("extra").Find(&category)
	if result.Error != nil {
		return nil, biz_error.DatabaseError.Wrap(errors.WithStack(result.Error))
	}
	return category, nil
}

func (r *repo) DeletePages(ctx context.Context, unique spec.BoardUnique, pageIds []string, isContinuous bool) error {
	if len(pageIds) == 0 {
		return nil
	}
	if isContinuous {
		return r.continueDeletePages(ctx, unique, pageIds)
	} else {
		return r.disperseDeletePages(ctx, unique, pageIds)
	}
}

func (r *repo) continueDeletePages(ctx context.Context, unique spec.BoardUnique, pageIds []string) error {
	var fistP []ddl.Pages
	query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TablePages).
		Where("app_id = ? and room_id = ? and board_id=? and page_id =?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), pageIds[0])
	err := query.Find(&fistP).Error
	if err != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}

	if len(fistP) != 1 {
		return biz_error.Conflict.Wrap(errors.Errorf("pageId:%s not exist or mange reocord ", pageIds[0]))
	}
	lastPage := pageIds[len(pageIds)-1]
	var lastPs []ddl.Pages
	query = r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TablePages).
		Where("app_id = ? and room_id = ? and board_id=? and pre_page_id=?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), lastPage)
	err = query.Find(&lastPs).Error
	if err != nil {
		return biz_error.DatabaseError.Wrap(errors.WithStack(err))
	}
	if len(lastPs) > 1 {
		return biz_error.UnknownError.Wrap(errors.Errorf("pre_page_id exist in many records"))
	}
	delStamp := time.Now().UnixNano() / 1000
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		for idx, pageId := range pageIds {
			prePageId := fistP[0].PrePageId
			if idx > 0 {
				prePageId = pageIds[idx-1]
			}
			now := time.Now()
			local, err := time.LoadLocation("Local")
			if err != nil {
				return biz_error.UnknownError.Wrap(err)
			}
			m := map[string]interface{}{
				"deleted_at":    now.In(local),
				"del_timestamp": delStamp,
			}
			//update 代替delete，因为要更新多字段
			result := tx.Table(ddl.TablePages).Where("app_id = ? and room_id = ? and board_id= ? and page_id= ? and pre_page_id = ?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), pageId, prePageId).Updates(
				m)

			if result.Error != nil {
				return biz_error.DatabaseError.Wrap(errors.WithStack(result.Error))
			}
			if result.RowsAffected == 0 {
				err = errors.Errorf("pageId or pre_page_id not correct")
				log.V1.CtxError(ctx, "continueDeletePages err:%+v", err)
				return biz_error.PacketReject.Wrap(err)
			}
		}
		if len(lastPs) > 0 {
			err = tx.Table(ddl.TablePages).Where("app_id = ? and room_id = ? and board_id = ? and page_id = ? and pre_page_id = ?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), lastPs[0].PageId, lastPs[0].PrePageId).Update(
				"pre_page_id", fistP[0].PrePageId).Error
			if err != nil {
				return biz_error.DatabaseError.Wrap(errors.WithStack(err))
			}
		}

		return nil

	})

}

// 分散页删除
func (r *repo) disperseDeletePages(ctx context.Context, unique spec.BoardUnique, pageIds []string) error {
	for _, pageId := range pageIds {
		err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
			var ps, nextPs []ddl.Pages
			query := r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TablePages).
				Where("app_id = ? and room_id = ? and board_id=? and page_id=?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), pageId)
			err := query.Find(&ps).Error
			if err != nil {
				return biz_error.DatabaseError.Wrap(errors.WithStack(err))
			}
			if len(ps) == 0 {
				return nil
			}
			if len(ps) > 1 {
				return biz_error.InvalidParam.Wrap(errors.WithStack(errors.Errorf("pageId:%s invalidc too many records", pageId)))
			}
			prePageId := ps[0].PrePageId
			query = r.db.WithContext(ctx).Clauses(dbresolver.Write).Table(ddl.TablePages).
				Where("app_id = ? and room_id = ? and board_id=? and pre_page_id=?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), pageId)
			err = query.Find(&nextPs).Error
			if err != nil {
				return biz_error.DatabaseError.Wrap(errors.WithStack(err))
			}
			if len(nextPs) > 1 {
				return biz_error.InvalidParam.Wrap(errors.WithStack(errors.Errorf("prePageId:%s invalid too many records", pageId)))
			}
			now := time.Now()
			local, err := time.LoadLocation("Local")
			if err != nil {
				return biz_error.UnknownError.Wrap(err)
			}
			m := map[string]interface{}{
				"deleted_at":    now.In(local),
				"del_timestamp": time.Now().UnixNano() / 1000,
			}
			//update 代替delete，因为要更新多字段
			err = tx.Table(ddl.TablePages).Where("app_id = ? and room_id = ? and board_id = ? and page_id= ? and pre_page_id= ?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), ps[0].PageId, ps[0].PrePageId).Updates(
				m).Error
			if err != nil {
				return biz_error.DatabaseError.Wrap(errors.WithStack(err))
			}
			if len(nextPs) > 0 {
				err = tx.Table(ddl.TablePages).Where("app_id = ? and room_id = ? and board_id = ? and page_id = ? and pre_page_id = ?", unique.GetAppId(), unique.GetRoomId(), unique.GetBoardId(), nextPs[0].PageId, nextPs[0].PrePageId).Update(
					"pre_page_id", prePageId).Error
				if err != nil {
					return biz_error.DatabaseError.Wrap(errors.WithStack(err))
				}
			}
			return nil
		})
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *repo) pageSort(ps []ddl.Pages) []ddl.Pages {
	preCurMap := make(map[string]string)
	curPages := make(map[string]*ddl.Pages)
	for idx, page := range ps {
		prePageId := page.PrePageId
		curPageId := page.PageId
		preCurMap[prePageId] = curPageId
		curPages[curPageId] = &ps[idx]
		if _, ok := curPages[prePageId]; !ok {
			curPages[prePageId] = nil
		}
	}
	var firstPrePageID = EmptyPrePageID
	for prePageId, page := range curPages {
		if page == nil {
			firstPrePageID = prePageId
			break
		}
	}
	ret := make([]ddl.Pages, 0, len(ps))
	curPrePageID := firstPrePageID
	for i := 0; i < len(preCurMap); i++ {
		if _, ok := preCurMap[curPrePageID]; !ok {
			break
		}
		curPageId := preCurMap[curPrePageID]
		ret = append(ret, *(curPages[curPageId]))
		curPrePageID = curPageId
	}
	return ret
}
